/*
 * Copyright (c) 2010, okazoh_tk. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  - Neither the name of the copyright holder nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "alkes/core/FileStream.h"
#include <stdio.h>

namespace alkes {

const static char* OPEN_READ_EXISTING = "rb";
const static char* OPEN_READ_WRITE_EXISTING = "rb+";
const static char* OPEN_WRITE_TRUNCATE = "wb";
const static char* OPEN_READ_WRITE_TRUNCATE = "wb+";

static const int SEEK[] = {
    SEEK_SET, SEEK_CUR, SEEK_END
};

#define FP(fp)    ((FILE*)fp)

FileStream::FileStream(const char* filepath, AccessMode accessmode, OpenMode openmode)
: Stream()
, fp_(0)
{
    openFile(filepath, accessmode, openmode);
}

FileStream::FileStream(const wchar_t* /*filepath*/, AccessMode /*accessmode*/, OpenMode /*openmode*/)
: Stream()
, fp_(0)
{
    AL_NO_SUPPORT(__FUNC_NAME__);
}

FileStream::~FileStream()
{
    closeFile();
}

int64_t FileStream::onGetLength()
{
    int64_t length = 0;

    if (fp_)
    {
        int64_t current = ftell(FP(fp_));
        if (current <= 0)
            return 0;

        if (0 != fseek(FP(fp_), 0, SEEK_END))
            return 0;

        int64_t end = ftell(FP(fp_));
        if (end <= 0)
            return 0;

        if (0 != fseek(FP(fp_), 0, SEEK_SET))
            return 0;

        int64_t start = ftell(FP(fp_));
        if (start < 0)
            return 0;

        length = end - start;
        (void)fseek(FP(fp_), (long)current, SEEK_SET);
    }

    return length;
}

int64_t FileStream::onGetPosition()
{
    int64_t position = 0;

    if (fp_)
    {
        position = ftell(FP(fp_));
    }

    return position;
}

int32_t FileStream::onRead(int32_t count, void* buffer, int32_t offset)
{
    return (int32_t)fread((uint8_t*)buffer + offset, (size_t)count, 1, FP(fp_));
}

int32_t FileStream::onWrite(int32_t count, const void* buffer, int32_t offset)
{
    return (int32_t)fwrite((const uint8_t*)buffer + offset, (size_t)count, 1, FP(fp_));
}

int64_t FileStream::onSeek(int64_t offset, SeekOrigin origin)
{
    if (origin < 0 || (int)AL_LENGTH(SEEK) <= origin)
        return -1;

    return fseek(FP(fp_), (long)offset, SEEK[origin]);
}

void FileStream::onFlush()
{
    (void)fflush(FP(fp_));
}

void FileStream::onClose()
{
    closeFile();
}

void FileStream::openFile(const char* filepath, AccessMode accessmode, OpenMode openmode)
{
    if (!filepath)
    {
        LOGE("filepath is NULL.");
        return ;
    }

    const char* flags = 0;
    if (AL_ACCESS_READ == accessmode && AL_OPEN_EXISTING == openmode)
        flags = OPEN_READ_EXISTING;
    else if (AL_ACCESS_WRITE == accessmode && AL_OPEN_TRUNCATE == openmode)
        flags = OPEN_WRITE_TRUNCATE;
    else if (AL_ACCESS_READWRITE == accessmode && AL_OPEN_EXISTING == openmode)
        flags = OPEN_READ_WRITE_EXISTING;
    else if (AL_ACCESS_READWRITE == accessmode && AL_OPEN_TRUNCATE == openmode)
        flags = OPEN_READ_WRITE_TRUNCATE;

    if (!flags)
    {
        LOGE("Invalid open mode.");
        return ; // Invalid mode.
    }

    fp_ = fopen(filepath, flags);

    if (fp_)
    {
        Attributes attrs = 0;

        if (accessmode & AL_ACCESS_READ)
            attrs |= READABLE;

        if (accessmode & AL_ACCESS_WRITE)
            attrs |= WRITABLE;

        changeAttributes(attrs|SEEKABLE);
    }
    else
    {
        LOGE("Can not open %s.", filepath);
    }
}

void FileStream::closeFile()
{
    if (fp_)
    {
        fclose(FP(fp_));
        fp_ = 0;
    }
}

}
